/** DirectX renderer for GSP410
**
** @version: $Id: Renderer.cpp 130 2010-12-05 00:46:49Z NeurobaticsTim@gmail.com $
*/
#include <assert.h>
#include "Renderer.h"
#include <dxerr.h>

#include "Colors.h"


/*****************************************************\
* Global variables: placed here for easy modification *
\*****************************************************/
const D3DXVECTOR3 cvInitialTarget = cvOrigin;	// viewing target in world coordinates
const D3DXVECTOR3 cvUp(0.0f, 1.0f, 0.0f);		// Up direction


/*****************************************************\
* Does file exist?                                    *
\*****************************************************/
bool FileExists(const char* cszFileName, const char* cszFunctionNameIfFails)
{
	/* Ask OS file system about file */
	bool bExists = (::GetFileAttributes(cszFileName) != 0xFFFFFFFF);
	if (! bExists)
	{
		/* No file: tell them */
		char szMessage[260];
		sprintf_s(szMessage, sizeof(szMessage), "File %s does not exist", cszFileName);

		/* MessageBox creates an error window and returns only when user clicks a button */
		::MessageBox(NULL, szMessage, cszFunctionNameIfFails, MB_ICONERROR);
	}
	return bExists;
}


/*****************************************************\
* Renderer constructor                                *
\*****************************************************/
Renderer::Renderer()
  : m_pD3DDevice(NULL)
  , m_hWnd(NULL)
  , m_vViewPoint(cvOrigin)
  , m_vTarget(cvInitialTarget)
  , m_fScreenWidth(0)
  , m_fScreenHeight(0)
  , m_pDefaultTexture(NULL)
  , m_pSprite(NULL)
  , m_eFillMode(D3DFILL_SOLID)
{
	/* Initialize member variables which can't be done in initializer list */
	D3DXMatrixIdentity(&m_mView);
	D3DXMatrixIdentity(&m_mProj);
	ZeroMemory(&m_xPresentParams, sizeof(m_xPresentParams));
	ZeroMemory(&m_cDefaultMaterial, sizeof(m_cDefaultMaterial));
	ZeroMemory(&m_cLight, sizeof(m_cLight));
}


/*****************************************************\
* Destructor                                          *
\*****************************************************/
Renderer::~Renderer()
{
	Release();
}


/*****************************************************\
* Initialize Direct3D in a window                     *
\*****************************************************/
void Renderer::InitializeD3D(HWND hWnd)
{
	/* D3D needs a window for drawing */
	if (hWnd == NULL)
	{
		return;
	}
	m_hWnd = hWnd;

	/* Set screen width and height to client rectangle */
	RECT rClientRect;
	if (! ::GetClientRect(m_hWnd, &rClientRect))
	{
		/* Can't get size of drawable area: a Bad Thing */
		throw "GetClientRect() failed";
	}
	m_fScreenWidth = (float)(rClientRect.right - rClientRect.left);
	m_fScreenHeight = (float)(rClientRect.bottom - rClientRect.top);

	/* Create D3D object */
	IDirect3D9* pD3D = Direct3DCreate9(D3D_SDK_VERSION);
	if (pD3D == NULL)
	{
		throw "Direct3DCreate9() failed";
	}

	/* Get default display mode */
	D3DDISPLAYMODE DisplayMode;
	HR(pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &DisplayMode));

	/* Get formats for that display mode */
	HR(pD3D->CheckDeviceFormat(D3DADAPTER_DEFAULT,
							   D3DDEVTYPE_HAL,
							   DisplayMode.Format,
							   D3DUSAGE_DEPTHSTENCIL,
							   D3DRTYPE_SURFACE,
							   D3DFMT_D16));

	/* Get video card capabilities */
	D3DCAPS9 D3DCaps;
	HR(pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &D3DCaps));

	/* Check device capabilities against requirements */
	DWORD dwBehaviorFlags = 0;
	/* Use hardware vertex processing if supported otherwise default to software */
	if (D3DCaps.VertexProcessingCaps != 0)
	{
		dwBehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
	}
	else
	{
		dwBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
	}

	/* All system checks passed, create the D3D device */
	ZeroMemory(&m_xPresentParams, sizeof(m_xPresentParams));

	m_xPresentParams.SwapEffect				= D3DSWAPEFFECT_DISCARD;
	m_xPresentParams.hDeviceWindow			= m_hWnd;
	m_xPresentParams.BackBufferFormat		= DisplayMode.Format;
	m_xPresentParams.Windowed				= TRUE;
	m_xPresentParams.PresentationInterval	= D3DPRESENT_INTERVAL_IMMEDIATE;
	m_xPresentParams.BackBufferWidth		= (int)m_fScreenWidth;
	m_xPresentParams.BackBufferHeight		= (int)m_fScreenHeight;
	m_xPresentParams.EnableAutoDepthStencil	= TRUE;
	m_xPresentParams.AutoDepthStencilFormat	= D3DFMT_D16;

	HR(pD3D->CreateDevice(D3DADAPTER_DEFAULT,
						  D3DDEVTYPE_HAL,
						  m_hWnd,
						  dwBehaviorFlags,
						  &m_xPresentParams,
						  &m_pD3DDevice));

	/* Release D3D object since no longer needed. Only require D3D device  */
	pD3D->Release();

	/* Set unchanging render states */
	HR(m_pD3DDevice->SetRenderState(D3DRS_LIGHTING, TRUE));	// turn on 3D lighting
	HR(m_pD3DDevice->SetRenderState(D3DRS_ZENABLE, TRUE));	// turn on z-buffer

	/* Make sprite object for drawing strings and sprites */
	HR(D3DXCreateSprite(m_pD3DDevice, &m_pSprite));
	assert(m_pSprite != NULL);
}


/*****************************************************\
* InitializeDemo                                      *
*   set up things specific to this particular program *
\*****************************************************/
void Renderer::InitializeDemo()
{
	/********************************************************\
	** D3D is initialized. Prepare some things for our scene *
	\********************************************************/
	CreateSceneLights();
	CreateMaterial();

	/* Create PERSPECTIVE transform which defines how 3D depth looks */
	D3DXMatrixPerspectiveFovLH(&m_mProj,
							   (D3DX_PI / 4),
							   (m_fScreenWidth / m_fScreenHeight),
							   1.0f,
							   1600.0f);
	HR(m_pD3DDevice->SetTransform(D3DTS_PROJECTION, &m_mProj));

	/* Create VIEW transform which defines position and orientation of the
	** view point (or camera)
	**
	**	(Change when eye location changes)
	*/
	D3DXMatrixLookAtLH(&m_mView, &m_vViewPoint, &m_vTarget, &cvUp);
	HR(m_pD3DDevice->SetTransform(D3DTS_VIEW, &m_mView));

	/* Set background ambient color */
	HR(m_pD3DDevice->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(150, 150, 150)));

	/* Set initial values for render states which might change */
	HR(m_pD3DDevice->SetRenderState(D3DRS_FILLMODE, m_eFillMode));
}


/*****************************************************\
* Release                                             *
\*****************************************************/
void Renderer::Release()
{
	if (m_pSprite != NULL)
	{
		m_pSprite->Release();
	}
	if (m_pD3DDevice != NULL)
	{
		m_pD3DDevice->Release();
	}
}


/*****************************************************\
* LoadTextureFile                                     *
\*****************************************************/
void Renderer::LoadTextureFile(const char* cszTextureFile, IDirect3DTexture9** ppTexture)
{
	/* Does the requested file exist? */
	if (! FileExists(cszTextureFile, "Renderer::LoadTextureFile"))
	{
		/* No file: don't try to load! */
		return;
	}

	HR(D3DXCreateTextureFromFile(m_pD3DDevice, cszTextureFile, ppTexture));
}


/*****************************************************\
* SetDefaultTexture                                   *
\*****************************************************/
void Renderer::SetDefaultTexture(const char* cszFilename)
{
	LoadTextureFile(cszFilename, &m_pDefaultTexture);
}


/*****************************************************\
* LoadMeshFile                                        *
\*****************************************************/
D3DXVECTOR3 Renderer::LoadMeshFile(Object* pObject, const char* cszXFile)
{
	assert((pObject != NULL) && (cszXFile != NULL));

	/* Does the requested file exist? */
	if (! FileExists(cszXFile, "Renderer::LoadMeshFile"))
	{
		/* No file: clean up and return */
		pObject->m_pMesh = NULL;
		return ZERO_VECTOR;
	}

	/* Empty mesh if not already empty */
	if (pObject->m_pMesh != NULL)
	{
		pObject->m_pMesh->Release();
	}
	pObject->m_pMesh = NULL;

	/* Prepare buffers returned by D3DXLoadMeshFromX */
	ID3DXBuffer* pAdjacency = NULL;			// required for OptimizeInPlace
	ID3DXBuffer* pMaterials = NULL;			// materials for each subset
	ID3DXBuffer* pEffectsInstances = NULL;	// (not interested in effects)
	DWORD dwNumMaterials = 0;				// number of materials stored in file

	/* Load the file into the mesh */
	HR(D3DXLoadMeshFromX(cszXFile,
						 D3DXMESH_MANAGED,
						 m_pD3DDevice,
						 &pAdjacency,
						 &pMaterials,
						 &pEffectsInstances,
						 &dwNumMaterials,
						 &pObject->m_pMesh));

	/* What did we get? */
	ID3DXMesh* pMesh = pObject->m_pMesh;
	assert(pMesh != NULL);

#ifdef _DEBUG
	/***** Display count of polys and verts in debug output window *****/
	char szLine[256];
	sprintf_s(szLine, sizeof(szLine),
			  "\n%s: %d faces, %d vertices, %d groups/materials\n",
			 cszXFile, pMesh->GetNumFaces(), pMesh->GetNumVertices(), dwNumMaterials);
	::OutputDebugString(szLine);
#endif

	/* Save the material and texture information which goes with each subset
	**
	** pMaterials is buffer holding array of D3DXMATERIAL entries
	** Put information from those entries in pObject's materials array
	*/
	D3DXMATERIAL* pEntry = (D3DXMATERIAL*)pMaterials->GetBufferPointer();
	unsigned n = 0;
	/* Using do loop so there will always be at least one entry in materials array */
	do
	{
		/* Set ambient to be same color as diffuse */
		pEntry->MatD3D.Ambient = pEntry->MatD3D.Diffuse;
		/* Jewels should be transparent */
		static const float cfTransparencyAlpha = 0.1f;
		pEntry->MatD3D.Ambient.a = cfTransparencyAlpha;
		pEntry->MatD3D.Diffuse.a = cfTransparencyAlpha;
		pEntry->MatD3D.Specular.a = cfTransparencyAlpha;
		pEntry->MatD3D.Emissive.a = cfTransparencyAlpha;

		/* Set reasonable default specular power */
		if (pEntry->MatD3D.Power == 0.0f)
		{
			pEntry->MatD3D.Power = 50.0f;
		}

#ifdef _DEBUG
		/***** Dump some info to the debug window *****/
		if (pEntry->pTextureFilename == NULL)
		{
			sprintf_s(szLine, sizeof(szLine), "  Material #%d: no texture\n", n);
		}
		else
		{
			sprintf_s(szLine, sizeof(szLine), "  Material #%d: texture \"%s\"\n",
					  n, pEntry->pTextureFilename);
		}
		::OutputDebugString(szLine);
		sprintf_s(szLine, sizeof(szLine), "    Ambient =(%g, %g, %g, %g)\n",
			pEntry->MatD3D.Ambient.r, pEntry->MatD3D.Ambient.g, pEntry->MatD3D.Ambient.b, pEntry->MatD3D.Ambient.a);
		::OutputDebugString(szLine);
		sprintf_s(szLine, sizeof(szLine), "    Diffuse =(%g, %g, %g, %g)\n",
			pEntry->MatD3D.Diffuse.r, pEntry->MatD3D.Diffuse.g, pEntry->MatD3D.Diffuse.b, pEntry->MatD3D.Diffuse.a);
		::OutputDebugString(szLine);
		sprintf_s(szLine, sizeof(szLine), "    Specular=(%g, %g, %g, %g)      Power=%g\n",
			pEntry->MatD3D.Specular.r, pEntry->MatD3D.Specular.g, pEntry->MatD3D.Specular.b, pEntry->MatD3D.Specular.a,
			pEntry->MatD3D.Power);
		::OutputDebugString(szLine);
		sprintf_s(szLine, sizeof(szLine), "    Emissive=(%g, %g, %g, %g)\n",
			pEntry->MatD3D.Emissive.r, pEntry->MatD3D.Emissive.g, pEntry->MatD3D.Emissive.b, pEntry->MatD3D.Emissive.a);
		::OutputDebugString(szLine);
		/**********************************************/
#endif

		/* Load specified texture using texturefilename */
		IDirect3DTexture9* pTexture = NULL;
		if (pEntry->pTextureFilename != NULL)
		{
			LoadTextureFile(pEntry->pTextureFilename, &pTexture);
		}

		/* Add new material information to pObjects materials list */
		pObject->AddMaterial(&(pEntry->MatD3D), pTexture);

		++pEntry;
	}
	while (++n < dwNumMaterials);

	/* What is flexible vertex format of vertex buffer before cloning? */
	DWORD dMeshFVF = pMesh->GetFVF();

	/* Clone mesh, ensuring room for position, normals, and texture coordinates */
	ID3DXMesh* pClone = NULL;
	HR(pObject->m_pMesh->CloneMeshFVF(D3DPOOL_MANAGED,
									  D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX2,
									  m_pD3DDevice,
									  &pClone));
	pObject->m_pMesh->Release();
	pObject->m_pMesh = pClone;

	/* Recompute normals */
	HR(D3DXComputeNormals(pObject->m_pMesh, (DWORD*)pAdjacency->GetBufferPointer()));

	/* Optimize mesh */
	HR(pObject->m_pMesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT |
											D3DXMESHOPT_COMPACT |
											D3DXMESHOPT_VERTEXCACHE,
										 (DWORD*)pAdjacency->GetBufferPointer(),
										 0, 0, 0));

	/* Find its center and bounding radius in local coordinates */
	GetBounds(pObject->m_pMesh, &(pObject->m_vOrigin), &(pObject->m_fBoundingRadius));

#ifdef _DEBUG
	sprintf_s(szLine, sizeof(szLine), "    Center at (%.0f, %.0f, %.0f). Radius is %0.2f.\n\n",
			  pObject->m_vOrigin.x, pObject->m_vOrigin.y, pObject->m_vOrigin.z,
			  pObject->m_fBoundingRadius);
	::OutputDebugString(szLine);
#endif

	/* Done with buffers */
	pAdjacency->Release();
	pEffectsInstances->Release();
	pMaterials->Release();

	/* return vector made up of interesting counts */
	return D3DXVECTOR3((float)pObject->m_pMesh->GetNumVertices(),
					   (float)pObject->m_pMesh->GetNumFaces(),
					   (float)dwNumMaterials);
}


/*****************************************************\
* Create scene lights                                 *
\*****************************************************/
void Renderer::CreateSceneLights()
{
	/* Enable specular lighting */
	HR(m_pD3DDevice->SetRenderState(D3DRS_SPECULARENABLE, TRUE));

	/* Create NUM_LIGHTS to start. More can be added later */
	for (unsigned n = 0; n < NUM_LIGHTS; ++n)
	{
		/* Define a directional light */
		D3DLIGHT9* pLight = &m_cLight[n];
		assert(pLight != NULL);
		ZeroMemory(pLight, sizeof(D3DLIGHT9));
		pLight->Type = D3DLIGHT_DIRECTIONAL;

		/* One white, another yellow */
		D3DXCOLOR cColor;
		switch (n)
		{
			case 0: cColor = WHITE; break;
			case 1: cColor = YELLOW; break;
		}
		/* Default component proportions */
		pLight->Ambient  = CScale(1.0f, cColor);
		pLight->Diffuse  = CScale(1.0f, cColor);
		pLight->Specular = CScale(0.8f, cColor);

		pLight->Position = cvOrigin;						// for spotlights and point lights
		pLight->Direction = m_vTarget - pLight->Position;	// for directional lights and spotlights

		pLight->Range = 1000;
		pLight->Falloff = 0;
		pLight->Attenuation0 = 1;
		pLight->Attenuation1 = 0;
		pLight->Attenuation2 = 0;

		m_pD3DDevice->SetLight(n, pLight);					// send properties to light
		m_pD3DDevice->LightEnable(n, TRUE);					// turn on light
	}
}


/*****************************************************\
* Create Material                                     *
\*****************************************************/
void Renderer::CreateMaterial()
{
	/* The default material is boring white */
	ZeroMemory(&m_cDefaultMaterial, sizeof(D3DMATERIAL9));

	m_cDefaultMaterial.Ambient  = CScale(0.36f, WHITE);	// ambient color: white
	m_cDefaultMaterial.Diffuse  = CScale(0.36f, WHITE);	// diffuse color: white
	m_cDefaultMaterial.Specular = CScale(0.28f, WHITE);	// specular color: white
	m_cDefaultMaterial.Power = 100;

	/* Tell graphics pipeline where to get specular light information */
	HR(m_pD3DDevice->SetRenderState(D3DRS_SPECULARMATERIALSOURCE, D3DMCS_MATERIAL));
}


/*****************************************************\
* SetViewPoint                                        *
\*****************************************************/
void Renderer::SetViewPoint(const D3DXVECTOR3& cvNew)
{
	m_vViewPoint = cvNew;

	/* Reset VIEW matrix:
	**		Create view transform, which defines position and orientation of
	**		view point (or "camera")
	*/
	D3DXMatrixLookAtLH(&m_mView, &m_vViewPoint, &m_vTarget, &cvUp);
	HR(m_pD3DDevice->SetTransform(D3DTS_VIEW, &m_mView));
}


/*****************************************************\
* RenderOneFrame                                      *
\*****************************************************/
void Renderer::RenderOneFrame(ObjectList* pvObjectList)
{
	if (IsLost())
	{
		/* Don't do any drawing until un-lost */
		return;
	}

	/* How big is the drawable area now? */
	::GetClientRect(m_hWnd, &m_rWindow);

	/*** Clear back buffer and z-buffer ***/
	HR(m_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, GRAY, 1.0f, 0));

	/*** Begin scene: start rendering everything to back buffer ***/
	HRESULT hr = m_pD3DDevice->BeginScene();
	if (FAILED(hr))
	{
		/* Stop rendering if BeginScene fails */
		return;
	}

	/*** Use sprite object to draw all text strings and sprites ***/
	if (m_pSprite != NULL)
	{
		m_pSprite->Begin(D3DXSPRITE_ALPHABLEND);

		/* Draw all text strings */
		RenderTexts();

		/* Draw all sprites */
		RenderSprites();

		/* Done when all strings and sprites are drawn */
		m_pSprite->End();
	}

	/*** Draw each object which wishes to be drawn ***/
	for each (Object* pObject in *pvObjectList)
	{
		assert(pObject != NULL);

		/* Objects know how to render themselves */
		pObject->Render();
	}

	/*** End the scene: drawing is all done ***/
	hr = m_pD3DDevice->EndScene();
    if (FAILED(hr))
    {
		/* Stop rendering if EndScene fails */
        return;
    }

	/*** Display back buffer on-screen ***/
	m_pD3DDevice->Present(NULL, NULL, NULL, NULL);
}


/*****************************************************\
* RenderObject                                        *
\*****************************************************/
void Renderer::RenderObject(Object* pObject)
{
	assert(pObject != NULL);

	/*** Tell D3D where to draw object. Each object has own WORLD transform ***/
	HR(m_pD3DDevice->SetTransform(D3DTS_WORLD, &(pObject->m_mWorld)));

	/*** Draw all subsets of mesh ***/
	const unsigned cnSubsets = pObject->m_vMaterials.size();
	/* Loop over all the subsets from the .X file */
	for (unsigned n = 0; n < cnSubsets; ++n)
	{
		/* Get material information for this subset from materials table */
		D3DMATERIAL9* pMaterial = NULL;
		IDirect3DTexture9* pTexture = NULL;
		pObject->GetMaterial(n, &pMaterial, &pTexture);

		/* Check for defaults and control flags */
		if (pMaterial == NULL)
		{
			pMaterial = &m_cDefaultMaterial;
		}
		assert(pMaterial != NULL);
		HR(m_pD3DDevice->SetMaterial(pMaterial));

		if (pTexture == NULL)
		{
			pTexture = m_pDefaultTexture;
		}
		if (pTexture != NULL)
		{
			HR(m_pD3DDevice->SetTexture(0, pTexture));
		}

		/* Draw mesh subset */
		HR(pObject->m_pMesh->DrawSubset(n));
	}
}


/*****************************************************\
* RenderText                                          *
\*****************************************************/
void Renderer::RenderTexts()
{
	/* Only draw texts if there is text to draw */
	if (! m_vTexts.empty())
	{
		/* Loop over all strings in list saved by DrawString */
		for each (TextBlock tb in m_vTexts)
		{
			/* Define screen rectangle where text will be drawn.
			** Biggest any text should be is size of entire window.
			*/
			RECT rText = m_rWindow;

			/* Negative values mean relative to the opposite side */
			rText.left = (tb.iX >= 0) ? tb.iX : m_rWindow.right + tb.iX;
			rText.top = (tb.iY >= 0) ? tb.iY : m_rWindow.bottom + tb.iY;

			/* Adjust rText rectangle to exactly fit text */
			tb.pFont->DrawText(m_pSprite, tb.szText, -1, &rText, DT_CALCRECT, tb.cColor);

			/* Draw string into rectangle */
			int iResult = tb.pFont->DrawText(m_pSprite, tb.szText, -1, &rText, 0, tb.cColor);

			/* Prevent memory leak by freeing duplicated text */
			delete tb.szText;
#if 0
			RECT rNextLine, rSameLine;

			/* Rectangle for next spot on same line */
			rSameLine = rText;
			rSameLine.left = rText.right + 2;	// a little space between strings
			rSameLine.right = m_rWindow.right;

			/* Rectangle for next line */
			rNextLine = m_rWindow;
			rNextLine.top = rText.bottom;
#endif
		}
		
		/* Clear out text strings to start next frame clean */ 
		m_vTexts.clear();
	}
}


/*****************************************************\
* IsLost                                              *
\*****************************************************/
bool Renderer::IsLost()
{
	/***** Watch for our window being covered, minimized or deactivated *****/

	/* If we have lost our D3DDevice pointer, just return */
	if (m_pD3DDevice == NULL)
	{
		return true;
	}

	/* Check for lost device */
	HRESULT hDevState = m_pD3DDevice->TestCooperativeLevel();
	switch (hDevState)
	{
		/*	S_OK: the device is ok for rendering.
		**	S_PRESENT_MODE_CHANGED: the display mode has changed.
		**	S_PRESENT_OCCLUDED: the window is occluded.
		**	D3DERR_DEVICELOST: the device has been lost.
		**	D3DERR_DEVICEHUNG: if the device is hung.
		**	D3DERR_DEVICEREMOVED: if the device has been removed.
		*/
		case S_OK:
			/* Everything is OK, not lost */
			return false;

		case D3DERR_DEVICENOTRESET:
			/* Finally have waited long enough; time to reset the D3D device */
			if (SUCCEEDED(m_pD3DDevice->Reset(&m_xPresentParams)))
			{
				/* TODO: Should reset the fonts here */
				return false;
			}
			::OutputDebugString("Reset failed");
			break;

		case S_PRESENT_OCCLUDED:
			/* Stop drawing until un-occluded */
			::OutputDebugString("Window occluded");
			break;

		case D3DERR_DEVICELOST:
			/* If the device is lost, return and check it again next frame */

			/* TODO: should call OnLost for fonts here */
			::OutputDebugString("Device lost");
			break;

		default:
			DXTRACE_ERR("TestCooperativeLevel()", hDevState);
			break;
	}

	/* Something was wrong! Stop drawing and check back but not too often */
	::Sleep(250);
	return true;
}


/*****************************************************\
* SetFillMode                                         *
\*****************************************************/
void Renderer::SetFillMode(FILL_MODE eMode)
{
	/* POINT_FILL, WIREFRAME, or SOLID */
	m_eFillMode = (D3DFILLMODE)eMode;
	HR(m_pD3DDevice->SetRenderState(D3DRS_FILLMODE, m_eFillMode));
}


/*****************************************************\
* Toggle Light Enable                                 *
\*****************************************************/
void Renderer::ToggleLightEnable(int iLightIndex)
{
	BOOL bEnabled;
	HR(m_pD3DDevice->GetLightEnable(iLightIndex, &bEnabled));
	
	/* Invert bEnabled */
	bEnabled = (bEnabled == FALSE);

	HR(m_pD3DDevice->LightEnable(iLightIndex, bEnabled));
}


/*****************************************************\
* Get Light Position                                  *
\*****************************************************/
D3DXVECTOR3 Renderer::GetLightPosition(unsigned nLightNumber)
{
	/* Retrieve light structure fields */
	D3DLIGHT9* pLight = &(m_cLight[nLightNumber]);
	assert(pLight != NULL);

	if (pLight->Type == D3DLIGHT_DIRECTIONAL)
	{
		return -1.0f * pLight->Direction;
	}
	else
	{
		return pLight->Position;
	}
}


/*****************************************************\
* Set Light Position                                  *
\*****************************************************/
void Renderer::SetLightPosition(unsigned nLightIndex, const D3DXVECTOR3& cvNewPosition)
{
	/* Set the light position in the structure we keep for each light */
	D3DLIGHT9* pLight = &(m_cLight[nLightIndex]);
	assert(pLight != NULL);

	/* (Special case for each different kind) */
	if (pLight->Type == D3DLIGHT_DIRECTIONAL)
	{
		pLight->Direction = -1.0f * cvNewPosition;
	}
	else
	{
		pLight->Position = cvNewPosition;
		if (pLight->Type == D3DLIGHT_SPOT)
		{
			pLight->Direction = m_vTarget - pLight->Position;
		}
	}

	/* Tell D3D that the light has changed. */
	HR(m_pD3DDevice->SetLight(nLightIndex, pLight));
}


/*****************************************************\
* Make font                                           *
\*****************************************************/
ID3DXFont* Renderer::MakeFont(const char* szTypeface,
							  int iHeight,
							  unsigned uWeight,
							  BOOL bItalic,
							  UINT uPitchAndFamily)
{
	ID3DXFont* pRetVal;
	/* Create the font object */
	HR(D3DXCreateFont(m_pD3DDevice,
					  -iHeight,				// match against character height instead of cell height
					  0,					// use screen aspect ratio for width
					  uWeight,				// FW_BOLD, FW_DONTCARE, FW_NORMAL, etc.
					  D3DX_DEFAULT,			// MipMap levels
					  bItalic,				// Italics?
					  DEFAULT_CHARSET,		// character set
					  OUT_DEFAULT_PRECIS,	// choose fonts however it wants
					  DEFAULT_QUALITY,		// matching quality
					  uPitchAndFamily,		// variable/fixed/default pitch and font family
					  szTypeface,			// requested typeface
					  &pRetVal));			// the returned font interface
	assert(pRetVal != NULL);

	return pRetVal;
}


/*****************************************************\
* DrawString                                          *
\*****************************************************/
void Renderer::DrawString(const char* szText, ID3DXFont* pFont, int iX, int iY, D3DCOLOR cColor)
{
	/* Queue everything in a list for the renderer to use the next time it draws. */
	TextBlock tb;

	tb.szText = _strdup(szText);	// duplicate string so pointer changing doesn't effect us
	tb.pFont = pFont;
	tb.iX = iX;
	tb.iY = iY;
	tb.cColor = cColor;
	InsertText(tb, m_vTexts);
}


/*****************************************************\
* Is Light On?                                        *
\*****************************************************/
bool Renderer::IsLightOn(int iLightIndex)
{
	BOOL bEnabled;
	HR(m_pD3DDevice->GetLightEnable(iLightIndex, &bEnabled));
	return (bEnabled != FALSE);
}


/*****************************************************\
* Get Bounds                                 *
\*****************************************************/
void Renderer::GetBounds(ID3DXMesh* pMesh, D3DXVECTOR3* pvCenter, float* pfSize)
{
	/* Get vertex count */
	DWORD dwNumVerts = pMesh->GetNumVertices();

	/* Get FVF flags */
	DWORD dwFVFSize = D3DXGetFVFVertexSize(pMesh->GetFVF());

	/* Lock vertex buffer */
	BYTE* pBytes;
	HR(pMesh->LockVertexBuffer(0, (LPVOID*)&pBytes));

	/* Compute bounding sphere */
	HR(D3DXComputeBoundingSphere((D3DXVECTOR3*)pBytes, dwNumVerts, dwFVFSize, pvCenter, pfSize));

	/* Unlock vertex buffer */
	HR(pMesh->UnlockVertexBuffer());
}


/*****************************************************\
* Get Light                                           *
\*****************************************************/
D3DLIGHT9* Renderer::GetLight(unsigned nLightNumber)
{
	return &(m_cLight[nLightNumber]);
}


/*****************************************************\
* Draw image on screen at specified location          *
\*****************************************************/
void Renderer::DrawSprite(IDirect3DTexture9* pTexture, int iX, int iY)
{
	assert(pTexture != NULL);

	SpriteBlock sb;
	sb.pTexture = pTexture;
	sb.iX = iX;
	sb.iY = iY;
	InsertSprite(sb, m_vSprites);
}


/*****************************************************\
* Render all the queued sprites                       *
\*****************************************************/
void Renderer::RenderSprites()
{
	/* Only draw if there are sprites waiting and a sprite object to draw them */
	if ((NULL != m_pSprite) && (! m_vSprites.empty()))
	{
		//RECT rScreen;
		//::GetClientRect(m_hWnd, &rScreen);
		unsigned uX, uY;

		for each (SpriteBlock sb in m_vSprites)
		{
			/* if X or Y are negative, make relative to other end/side */
			uX = (sb.iX >= 0) ? sb.iX : m_rWindow.right + sb.iX;
			uY = (sb.iY >= 0) ? sb.iY : m_rWindow.bottom + sb.iY;

			/* Where shall sprite be drawn? z=0 means on the glass */
			const D3DXVECTOR3 vPosition((float)uX, (float)uY, 0.0f);

			/* Draw sprite using all given texture and at vPosition */
			HR(m_pSprite->Draw(sb.pTexture, NULL, NULL, &vPosition, 0xFFFFFFFF));
		}
		
		/* Clear out sprites to start next frame clean */ 
		m_vSprites.clear();
	}
}

